﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using snat.algorithms.network;
using System.Windows.Threading;
using System.Globalization;
using snat.algorithms;
using algorithms;
using Microsoft.Win32;

namespace gui {
    /// <summary>
    /// Interaction logic for AlgorithmsTab.xaml (Named Network Tab on GUI)
    /// </summary>
    public partial class AlgorithmsTab : UserControl {

        public static event guiOnAlgorithmCustomWrite OnAlgorithmCustomWrite;
        public static event guiOnAlgorithmCustomRead OnAlgorithmCustomRead;
        public static event guiOnAlgorithmConsoleSave OnAlgorithmConsoleSave;

        public ObservableCollection<AlgorithmItem> AlgorithmsList { get; set; }
        private List<string> registeredAlgorithms = new List<string>();
        private static Dictionary<string, List<string>> algorithmOutput = new Dictionary<string, List<string>>();

        public AlgorithmsTab() {
            InitializeComponent();

            // Build algorithms list
            AlgorithmsList = new ObservableCollection<AlgorithmItem>();
            Initialise.AlgorithmsListBuiltEvent += PopulateFilterList;
            this.DataContext = this;

            // Register GUI enable/disable events
            NetworkModelConnect.OnConnectingEvent += DisableCheckBoxes;
            NetworkModelConnect.OnConnectedEvent += DisableCheckBoxes;
            NetworkModelConnect.TapeLoadingEvent += DisableCheckBoxes;
            NetworkModelConnect.TapeLoadedEvent += EnableCheckBoxes;
            NetworkModelConnect.OnFinishedProcessingEvent += EnableCheckBoxes;
            PlayControl.RunningEvent += DisableCheckBoxes;
            PlayControl.ReadyEvent += EnableCheckBoxes;
            PlayControl.FinishedEvent += DisableCheckBoxes;

            // Register Algorithm Custom File Read/Write Events
            OnAlgorithmCustomWrite += WriteAlgorithmCustomOutput;
            OnAlgorithmCustomRead += ReadAlgorithmCustomFile;
            OnAlgorithmConsoleSave += SaveAlgorithmConsoleOutput;

        }

        private static void AlgorithmRender(IAlgorithm alg) {
            String renderString;
            if (alg is ITextRenderable) {
                renderString = String.Join("", "\n--------------- ", alg.Name, " ---------------\n", (alg as ITextRenderable).GetRenderableText());
                algorithmOutput[alg.Name].Add((alg as ITextRenderable).GetRenderableText());  // Add string to algorithmOutput
            } else {
                renderString = String.Join("", "Algorithm '", alg.Name, "' does not implement IRenderable so cannot produce textural output.");
            }
            ConsolePane.WriteLine(renderString);
        }

        // Have the dispatcher build the filter list
        public void PopulateFilterList(ObservableCollection<AlgorithmItem> list) {
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                new Action(delegate() {
                    BuildFilterList(list);
            }));
        }

        // Populate Algorithm List list with selected algorithms from AlgorithmBox
        public void BuildFilterList(ObservableCollection<AlgorithmItem> list) {
            AlgorithmsList.Clear();
            algorithmOutput.Clear();
            registeredAlgorithms.Clear();

            if (list.Count > 0) {
                foreach (AlgorithmItem a in list) {
                    AlgorithmsList.Add(a);
                    try {
                        algorithmOutput.Add(a.Algorithm.Name, new List<string>());
                    } catch (ArgumentException ex) {
                        // If the algorithm key already exists in the output then we cannot distinguish the difference between the
                        // two algorithms, so close the program
                        MessageBoxResult answer = MessageBox.Show("Multiple algorithms with the same name have been registered. The system cannot distinguish the different between them and so has to close. Please ensure IAlgorithm and AlgorithmSet names match, and that multiple algorithm instantiations have different names.", "Algorithm Name Error", MessageBoxButton.OK, MessageBoxImage.Error);
                        if (answer == MessageBoxResult.OK) Application.Current.Shutdown();
                    }
                }
                foreach (AlgorithmItem a in AlgorithmsList) {
                    a.Selected = true;
                    registeredAlgorithms.Add(a.Algorithm.Name);
                }

                // Add algorithm listeners
                foreach (AlgorithmItem a in AlgorithmsList) {
                    if (a.Algorithm is ITextRenderable) {
                        (a.Algorithm as ITextRenderable).RenderText += AlgorithmRender;
                    }
                }
            }
        }

        // Register Algorithm
        private void CheckBox_Checked(object sender, RoutedEventArgs e) {
            foreach (AlgorithmItem a in AlgorithmsList) {
                if (a.Selected == true && !registeredAlgorithms.Contains(a.Name)) {
                    registeredAlgorithms.Add(a.Name);
                    AlgorithmSet.Instance.TryRegister(a.Name);
                    ConsolePane.WriteLine("Registering " + a.Name);
                }
            }
        }

        // Deregister Algorithm
        private void CheckBox_Unchecked(object sender, RoutedEventArgs e) {
            foreach (AlgorithmItem a in AlgorithmsList) {
                if (a.Selected == false && registeredAlgorithms.Contains(a.Name)) {
                    registeredAlgorithms.Remove(a.Name);
                    AlgorithmSet.Instance.TryDeRegister(a.Name);
                    ConsolePane.WriteLine("Deregistering " + a.Name);
                }
            }
        }

        // Save out the whole console to file
        public static void SaveConsoleOutput(string filename) {
            if (filename != null && filename != String.Empty) {
                string[] delimiters = new string[] { Environment.NewLine, "\r\n", "\n" };
                string[] lines = ConsoleConnect.Instance.Contents.Split(delimiters, StringSplitOptions.None);

                System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
                foreach (string line in lines) {
                    file.WriteLine(line);
                }
                file.Close();
            }
        }

        // Save out individual algorithm console output to separate files
        public static void SaveAlgorithmConsoleOutput(string directory) {
            if (directory != null && directory != String.Empty) {
                foreach (KeyValuePair<string, List<string>> output in algorithmOutput) {
                    // If there are no output entries for this algorithm then skip it
                    if (output.Value.Count() > 0) {
                        string content = "";
                        foreach (string str in output.Value) {
                            content = String.Join("\n", content, str);
                        }
                        System.IO.StreamWriter file = new System.IO.StreamWriter(directory + "/" + output.Key + ".txt");
                        file.WriteLine(content);
                        file.Close();
                    }
                }
            }
        }

        // Call to write out individual algorithm custom output to separate files
        public static void CallSaveAlgorithmConsoleOutput() {
            if (OnAlgorithmConsoleSave != null) OnAlgorithmConsoleSave();
        }

        // Write out individual algorithm custom output to file
        public void SaveAlgorithmConsoleOutput() {

            // Build list of Algorithms
            Dictionary<string, IAlgorithm> selectionList = new Dictionary<string, IAlgorithm>();
            foreach (AlgorithmItem a in AlgorithmsList) {
                selectionList.Add(a.Name, a.Algorithm);
            }
            if (selectionList.Count < 1) {
                MessageBox.Show("Cannot write out console output file as no algorithms were registered at Network Model Initialisation.", "There is nothing to save", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            // Have the user specify the algorithm they want in an algorithmSelectBox
            string selected = "";
            AlgorithmsSelectBox box = new AlgorithmsSelectBox(selectionList, false);
            box.ShowDialog(); // Wait for box to return
            selected = box.SelectedList.First().Value.Name;

            // Have the user specify the output filename
            string filename = "";
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.DefaultExt = ".txt";
            dlg.Filter = "Text documents (.txt)|*.txt";
            if (dlg.ShowDialog() == true) {
                filename = dlg.FileName;
            }

            // If valid filename, save out the algorithm
            if (filename != null && filename != String.Empty) {

                // If there are no output entries for this algorithm then display message box
                if (algorithmOutput[selected].Count() > 0) {
                    string content = "";
                    foreach (string str in algorithmOutput[selected]) {
                        content = String.Join("\n", content, str);
                    }
                    System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
                    file.WriteLine(content);
                    file.Close();
                } else {
                    MessageBox.Show("Cannot write out console output file as there is no console output from the selected algorithm.", "There is nothing to save", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }
                
            } else {
                MessageBox.Show("Invalid filename. Please provide a valid filename.", "Invalid Filename", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }

        }

        // Call to write out individual algorithm custom output to separate files
        public static void CallWriteAlgorithmCustomOutput() {
            if (OnAlgorithmCustomWrite != null) OnAlgorithmCustomWrite();
        }

        // Write out individual algorithm custom output to file
        public void WriteAlgorithmCustomOutput() {

            // Build list of IReaderWriter Algorithms
            Dictionary<string, IAlgorithm> listOfIReaderWriters = new Dictionary<string, IAlgorithm>();
            foreach (string a in registeredAlgorithms) {
                if (AlgorithmSet.Instance[a] is IReaderWriter) {
                    listOfIReaderWriters.Add(AlgorithmSet.Instance[a].Name, AlgorithmSet.Instance[a]);
                }
            }
            if (listOfIReaderWriters.Count < 1) {
                MessageBox.Show("Cannot write out custom algorithm output file as there are no currently registered algorithms that implement the IReaderWriter interface.", "No IReaderWriter Interface for Registered Algorithms", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            // Have the user specify the algorithm they want in an algorithmSelectBox
            string selected = "";
            AlgorithmsSelectBox box = new AlgorithmsSelectBox(listOfIReaderWriters, false);
            box.ShowDialog(); // Wait for box to return
            selected = box.SelectedList.First().Value.Name;   

            // Have the user specify the output filename
            string filename = "";
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.DefaultExt = ".txt";
            dlg.Filter = "Text documents (.txt)|*.txt";
            if (dlg.ShowDialog() == true) {
                filename = dlg.FileName;
            }

            // If valid filename, call algorithm's Write method
            if (filename != null && filename != String.Empty && (AlgorithmSet.Instance[selected] is IReaderWriter)) {
                (AlgorithmSet.Instance[selected] as IReaderWriter).Write(filename);
            } else {
                MessageBox.Show("Invalid filename. Please provide a valid filename.", "Invalid Filename", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }

        }



        // Read in algorithm custom output
        public static void CallReadAlgorithmCustomFile() {
            if (OnAlgorithmCustomRead != null) OnAlgorithmCustomRead();
        }

        // Read in individual algorithm custom output
        public void ReadAlgorithmCustomFile() {
 
            // Build list of IReaderWriter Algorithms
            Dictionary<string, IAlgorithm> listOfIReaderWriters = new Dictionary<string, IAlgorithm>();
            foreach (string a in registeredAlgorithms) {
                if (AlgorithmSet.Instance[a] is IReaderWriter) {
                    listOfIReaderWriters.Add(AlgorithmSet.Instance[a].Name, AlgorithmSet.Instance[a]);
                }
            }
            if (listOfIReaderWriters.Count < 1) {
                MessageBox.Show("Cannot read in custom algorithm file as there are no currently registered algorithms that implement the IReaderWriter interface.", "No IReaderWriter Interface for Registered Algorithms", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            // Have the user specify the algorithm they want in an algorithmSelectBox
            string selected = "";
            AlgorithmsSelectBox box = new AlgorithmsSelectBox(listOfIReaderWriters, false);
            box.ShowDialog(); // Wait for box to return
            selected = box.SelectedList.First().Value.Name;   

            // Have the user specify the output filename
            string filename = "";
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.DefaultExt = ".txt";
            dlg.Filter = "Text documents (.txt)|*.txt";
            if (dlg.ShowDialog() == true) {
                filename = dlg.FileName;
            }

            // If valid filename, call algorithm's read method
            if (filename != null && filename != String.Empty && (AlgorithmSet.Instance[selected] is IReaderWriter)) {
                ConsolePane.WriteLine(String.Join("", "\n--------------- ", selected, " ---------------\n", (AlgorithmSet.Instance[selected] as IReaderWriter).Read(filename)));
            } else {
                MessageBox.Show("Invalid filename. Please provide a valid filename.", "Invalid Filename", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }
        }


        // Lock and Unlock Algorithm Selection Checkboxes (for when processing)
        private void DisableCheckBoxes() {
            foreach (AlgorithmItem a in AlgorithmsList) a.Enabled = false;
        }
        private void EnableCheckBoxes() {
            foreach (AlgorithmItem a in AlgorithmsList) a.Enabled = true;
        }
    }
}
